searchkick 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +6 -0
- data/README.md +73 -104
- data/lib/searchkick.rb +31 -57
- data/lib/searchkick/index.rb +15 -6
- data/lib/searchkick/index_options.rb +4 -1
- data/lib/searchkick/indexer.rb +28 -0
- data/lib/searchkick/logging.rb +17 -14
- data/lib/searchkick/model.rb +21 -13
- data/lib/searchkick/version.rb +1 -1
- data/test/boost_test.rb +1 -1
- data/test/gemfiles/mongoid6.gemfile +6 -0
- data/test/highlight_test.rb +3 -3
- data/test/index_test.rb +3 -3
- data/test/partial_reindex_test.rb +2 -2
- data/test/sql_test.rb +6 -7
- data/test/test_helper.rb +5 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fa47681f8cf22fcc751a944326bfbd16f437a55
|
4
|
+
data.tar.gz: 270f29030b412fb937c33c04e00069982c48e3c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a721f818961659f9d3337573c52da0414db81040d223830f5ca05d8fda59035f4580de17bab411331ea7939bf98795fe60a8d25bd2fa8375b25370f74cee4551
|
7
|
+
data.tar.gz: 5ba8046de966775866b9015f3506aa4d9d6a6c2e8a83536adc847a31b94754a395fec203530367b95bed518f481bff689e6c52d80f0c34758ca72908d888eee8
|
data/.travis.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
|
-
rvm: 2.
|
3
|
+
rvm: 2.3.1
|
4
4
|
services:
|
5
5
|
- mongodb
|
6
6
|
before_install:
|
@@ -23,6 +23,7 @@ gemfile:
|
|
23
23
|
- test/gemfiles/mongoid3.gemfile
|
24
24
|
- test/gemfiles/mongoid4.gemfile
|
25
25
|
- test/gemfiles/mongoid5.gemfile
|
26
|
+
- test/gemfiles/mongoid6.gemfile
|
26
27
|
env:
|
27
28
|
- ELASTICSEARCH_VERSION=5.1.1
|
28
29
|
jdk: oraclejdk8
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -343,7 +343,7 @@ You can change this with:
|
|
343
343
|
Product.search "zucini", misspellings: {edit_distance: 2} # zucchini
|
344
344
|
```
|
345
345
|
|
346
|
-
To 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.
|
346
|
+
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.
|
347
347
|
|
348
348
|
```ruby
|
349
349
|
Product.search "zuchini", misspellings: {below: 5}
|
@@ -575,7 +575,7 @@ end
|
|
575
575
|
Reindex and search with:
|
576
576
|
|
577
577
|
```ruby
|
578
|
-
Book.search "tipping poi"
|
578
|
+
Book.search "tipping poi", match: :word_start
|
579
579
|
```
|
580
580
|
|
581
581
|
Typically, you want to use a JavaScript library like [typeahead.js](http://twitter.github.io/typeahead.js/) or [jQuery UI](http://jqueryui.com/autocomplete/).
|
@@ -926,25 +926,9 @@ class City < ActiveRecord::Base
|
|
926
926
|
end
|
927
927
|
```
|
928
928
|
|
929
|
-
|
929
|
+
See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for details.
|
930
930
|
|
931
|
-
|
932
|
-
|
933
|
-
* **point**: single lat/lon pair
|
934
|
-
* **multipoint**: array of points
|
935
|
-
* **linestring**: array of at least two lat/lon pairs
|
936
|
-
* **multilinestring**: array of lines
|
937
|
-
* **polygon**: an array of paths, each being an array of at least four lat/lon pairs whose first and last points are the same. Paths after the first represent exclusions. Elasticsearch will return an error if a polygon contains two consecutive identical points, intersects itself or is not closed.
|
938
|
-
* **multipolygon**: array of polygons
|
939
|
-
* **envelope**: a bounding box defined by top left and bottom right points
|
940
|
-
* **circle**: a bounding circle defined by center point and radius
|
941
|
-
* **geometrycollection**: an array of separate GeoJSON objects possibly of various types
|
942
|
-
|
943
|
-
See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for details. GeoJSON coordinates are usually given as an array of `[lon, lat]` points but searchkick can also take objects with `lon` and `lat` keys.
|
944
|
-
|
945
|
-
Once a geo shape index is established, you can include a geo shape filter in any search. This also takes a GeoJSON shape and will return a list of items based on their overlap with that shape.
|
946
|
-
|
947
|
-
Find shapes (of any kind) intersecting with the query shape
|
931
|
+
Find shapes intersecting with the query shape
|
948
932
|
|
949
933
|
```ruby
|
950
934
|
City.search "san", where: {bounds: {geo_shape: {type: "polygon", coordinates: [[{lat: 38, lon: -123}, ...]]}}}
|
@@ -968,26 +952,6 @@ Containing the query shape (Elasticsearch 2.2+)
|
|
968
952
|
City.search "san", where: {bounds: {geo_shape: {type: "envelope", relation: "contains", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}}
|
969
953
|
```
|
970
954
|
|
971
|
-
### Routing
|
972
|
-
|
973
|
-
Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
|
974
|
-
|
975
|
-
```ruby
|
976
|
-
class Business < ActiveRecord::Base
|
977
|
-
searchkick routing: true
|
978
|
-
|
979
|
-
def search_routing
|
980
|
-
city_id
|
981
|
-
end
|
982
|
-
end
|
983
|
-
```
|
984
|
-
|
985
|
-
Reindex and search with:
|
986
|
-
|
987
|
-
```ruby
|
988
|
-
Business.search "ice cream", routing: params[:city_id]
|
989
|
-
```
|
990
|
-
|
991
955
|
## Inheritance
|
992
956
|
|
993
957
|
Searchkick supports single table inheritance.
|
@@ -1137,7 +1101,37 @@ Then deploy and reindex:
|
|
1137
1101
|
rake searchkick:reindex CLASS=Product
|
1138
1102
|
```
|
1139
1103
|
|
1140
|
-
###
|
1104
|
+
### Automatic Failover
|
1105
|
+
|
1106
|
+
Create an initializer `config/initializers/elasticsearch.rb` with multiple hosts:
|
1107
|
+
|
1108
|
+
```ruby
|
1109
|
+
ENV["ELASTICSEARCH_URL"] = "http://localhost:9200,http://localhost:9201"
|
1110
|
+
|
1111
|
+
Searchkick.client_options = {
|
1112
|
+
retry_on_failure: true
|
1113
|
+
}
|
1114
|
+
```
|
1115
|
+
|
1116
|
+
See [elasticsearch-transport](https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-transport) for a complete list of options.
|
1117
|
+
|
1118
|
+
### Lograge
|
1119
|
+
|
1120
|
+
Add the following to `config/environments/production.rb`:
|
1121
|
+
|
1122
|
+
```ruby
|
1123
|
+
config.lograge.custom_options = lambda do |event|
|
1124
|
+
options = {}
|
1125
|
+
options[:search] = event.payload[:searchkick_runtime] if event.payload[:searchkick_runtime].to_f > 0
|
1126
|
+
options
|
1127
|
+
end
|
1128
|
+
```
|
1129
|
+
|
1130
|
+
See [Production Rails](https://github.com/ankane/production_rails) for other good practices.
|
1131
|
+
|
1132
|
+
## Performance
|
1133
|
+
|
1134
|
+
### Persistent HTTP Connections
|
1141
1135
|
|
1142
1136
|
For the best performance, add [Typhoeus](https://github.com/typhoeus/typhoeus) to your Gemfile.
|
1143
1137
|
|
@@ -1145,38 +1139,53 @@ For the best performance, add [Typhoeus](https://github.com/typhoeus/typhoeus) t
|
|
1145
1139
|
gem 'typhoeus'
|
1146
1140
|
```
|
1147
1141
|
|
1148
|
-
And create an initializer with:
|
1142
|
+
And create an initializer to reduce log noise with:
|
1149
1143
|
|
1150
1144
|
```ruby
|
1151
|
-
|
1152
|
-
Ethon.logger.level = Logger::WARN
|
1145
|
+
Ethon.logger = Logger.new("/dev/null")
|
1153
1146
|
```
|
1154
1147
|
|
1155
1148
|
If you run into issues on Windows, check out [this post](https://www.rastating.com/fixing-issues-in-typhoeus-and-httparty-on-windows/).
|
1156
1149
|
|
1157
|
-
###
|
1150
|
+
### Searchable Fields
|
1158
1151
|
|
1159
|
-
|
1152
|
+
By default, all string fields are searchable. Speed up indexing and reduce index size by only making some fields searchable.
|
1160
1153
|
|
1161
1154
|
```ruby
|
1162
|
-
|
1155
|
+
class Product < ActiveRecord::Base
|
1156
|
+
searchkick searchable: [:name]
|
1157
|
+
end
|
1163
1158
|
```
|
1164
1159
|
|
1165
|
-
|
1160
|
+
### Filterable Fields
|
1166
1161
|
|
1167
|
-
|
1162
|
+
By default, all fields are filterable (can be used in `where` option). Speed up indexing and reduce index size by only making some fields filterable.
|
1168
1163
|
|
1169
|
-
|
1164
|
+
```ruby
|
1165
|
+
class Product < ActiveRecord::Base
|
1166
|
+
searchkick filterable: [:store_id]
|
1167
|
+
end
|
1168
|
+
```
|
1169
|
+
|
1170
|
+
### Routing
|
1171
|
+
|
1172
|
+
Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
|
1170
1173
|
|
1171
1174
|
```ruby
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1175
|
+
class Business < ActiveRecord::Base
|
1176
|
+
searchkick routing: true
|
1177
|
+
|
1178
|
+
def search_routing
|
1179
|
+
city_id
|
1180
|
+
end
|
1176
1181
|
end
|
1177
1182
|
```
|
1178
1183
|
|
1179
|
-
|
1184
|
+
Reindex and search with:
|
1185
|
+
|
1186
|
+
```ruby
|
1187
|
+
Business.search "ice cream", routing: params[:city_id]
|
1188
|
+
```
|
1180
1189
|
|
1181
1190
|
## Advanced
|
1182
1191
|
|
@@ -1271,7 +1280,7 @@ indices_boost: {Category => 2, Product => 1}
|
|
1271
1280
|
To query nested data, use dot notation.
|
1272
1281
|
|
1273
1282
|
```ruby
|
1274
|
-
User.search "
|
1283
|
+
User.search "san", fields: ["address.city"], where: {"address.zip_code" => 12345}
|
1275
1284
|
```
|
1276
1285
|
|
1277
1286
|
## Reference
|
@@ -1304,7 +1313,7 @@ Product.where("id > 100000").find_in_batches do |batch|
|
|
1304
1313
|
end
|
1305
1314
|
```
|
1306
1315
|
|
1307
|
-
Reindex a subset of attributes
|
1316
|
+
Reindex a subset of attributes (partial reindex)
|
1308
1317
|
|
1309
1318
|
```ruby
|
1310
1319
|
class Product < ActiveRecord::Base
|
@@ -1316,13 +1325,13 @@ class Product < ActiveRecord::Base
|
|
1316
1325
|
end
|
1317
1326
|
end
|
1318
1327
|
|
1319
|
-
Product.
|
1328
|
+
Product.reindex(:search_prices)
|
1320
1329
|
```
|
1321
1330
|
|
1322
1331
|
Remove old indices
|
1323
1332
|
|
1324
1333
|
```ruby
|
1325
|
-
Product.clean_indices
|
1334
|
+
Product.searchkick_index.clean_indices
|
1326
1335
|
```
|
1327
1336
|
|
1328
1337
|
Use custom settings
|
@@ -1369,7 +1378,7 @@ class Product < ActiveRecord::Base
|
|
1369
1378
|
def search_data
|
1370
1379
|
{
|
1371
1380
|
name: name,
|
1372
|
-
unique_user_conversions: searches.group(:query).uniq.count(:user_id)
|
1381
|
+
unique_user_conversions: searches.group(:query).uniq.count(:user_id),
|
1373
1382
|
# {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
|
1374
1383
|
total_conversions: searches.group(:query).count
|
1375
1384
|
# {"ice cream" => 412, "chocolate" => 117, "cream" => 6}
|
@@ -1419,11 +1428,11 @@ class Product < ActiveRecord::Base
|
|
1419
1428
|
end
|
1420
1429
|
```
|
1421
1430
|
|
1422
|
-
Use [
|
1431
|
+
Use a different [similarity algorithm](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-similarity.html) for scoring
|
1423
1432
|
|
1424
1433
|
```ruby
|
1425
1434
|
class Product < ActiveRecord::Base
|
1426
|
-
searchkick similarity: "
|
1435
|
+
searchkick similarity: "classic"
|
1427
1436
|
end
|
1428
1437
|
```
|
1429
1438
|
|
@@ -1454,14 +1463,6 @@ Add [request parameters](https://www.elastic.co/guide/en/elasticsearch/reference
|
|
1454
1463
|
Product.search("carrots", request_params: {search_type: "dfs_query_then_fetch"})
|
1455
1464
|
```
|
1456
1465
|
|
1457
|
-
Make fields unsearchable but include in the source
|
1458
|
-
|
1459
|
-
```ruby
|
1460
|
-
class Product < ActiveRecord::Base
|
1461
|
-
searchkick unsearchable: [:color]
|
1462
|
-
end
|
1463
|
-
```
|
1464
|
-
|
1465
1466
|
Reindex conditionally
|
1466
1467
|
|
1467
1468
|
**Note:** With ActiveRecord, use this feature with caution - [transaction rollbacks can cause data inconsistencies](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/README.md#custom-callbacks)
|
@@ -1471,7 +1472,7 @@ class Product < ActiveRecord::Base
|
|
1471
1472
|
searchkick callbacks: false
|
1472
1473
|
|
1473
1474
|
# add the callbacks manually
|
1474
|
-
after_save :reindex, if:
|
1475
|
+
after_save :reindex, if: -> (model) { model.name_changed? } # use your own condition
|
1475
1476
|
after_destroy :reindex
|
1476
1477
|
end
|
1477
1478
|
```
|
@@ -1508,7 +1509,6 @@ This section could use some love.
|
|
1508
1509
|
describe Product do
|
1509
1510
|
it "searches" do
|
1510
1511
|
Product.reindex
|
1511
|
-
Product.searchkick_index.refresh # don't forget this
|
1512
1512
|
# test goes here...
|
1513
1513
|
end
|
1514
1514
|
end
|
@@ -1518,44 +1518,13 @@ end
|
|
1518
1518
|
|
1519
1519
|
```ruby
|
1520
1520
|
product = FactoryGirl.create(:product)
|
1521
|
-
product.reindex
|
1522
|
-
Product.searchkick_index.refresh # or this
|
1521
|
+
product.reindex(refresh: true)
|
1523
1522
|
```
|
1524
1523
|
|
1525
1524
|
## Multi-Tenancy
|
1526
1525
|
|
1527
1526
|
Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenancy-with-searchkick/) on the [Apartment](https://github.com/influitive/apartment) gem. Follow a similar pattern if you use another gem.
|
1528
1527
|
|
1529
|
-
## Migrating from Tire
|
1530
|
-
|
1531
|
-
1. Change `search` methods to `tire.search` and add index name in existing search calls
|
1532
|
-
|
1533
|
-
```ruby
|
1534
|
-
Product.search "fruit"
|
1535
|
-
```
|
1536
|
-
|
1537
|
-
should be replaced with
|
1538
|
-
|
1539
|
-
```ruby
|
1540
|
-
Product.tire.search "fruit", index: "products"
|
1541
|
-
```
|
1542
|
-
|
1543
|
-
2. Replace tire mapping w/ searchkick method
|
1544
|
-
|
1545
|
-
```ruby
|
1546
|
-
class Product < ActiveRecord::Base
|
1547
|
-
searchkick
|
1548
|
-
end
|
1549
|
-
```
|
1550
|
-
|
1551
|
-
3. Deploy and reindex
|
1552
|
-
|
1553
|
-
```ruby
|
1554
|
-
rake searchkick:reindex CLASS=Product # or Product.reindex in the console
|
1555
|
-
```
|
1556
|
-
|
1557
|
-
4. Once it finishes, replace search calls w/ searchkick calls
|
1558
|
-
|
1559
1528
|
## Upgrading
|
1560
1529
|
|
1561
1530
|
View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md).
|
data/lib/searchkick.rb
CHANGED
@@ -4,6 +4,7 @@ require "hashie"
|
|
4
4
|
require "searchkick/version"
|
5
5
|
require "searchkick/index_options"
|
6
6
|
require "searchkick/index"
|
7
|
+
require "searchkick/indexer"
|
7
8
|
require "searchkick/results"
|
8
9
|
require "searchkick/query"
|
9
10
|
require "searchkick/reindex_job"
|
@@ -11,6 +12,7 @@ require "searchkick/model"
|
|
11
12
|
require "searchkick/tasks"
|
12
13
|
require "searchkick/middleware"
|
13
14
|
require "searchkick/logging" if defined?(ActiveSupport::Notifications)
|
15
|
+
require "active_support/core_ext/hash/deep_merge"
|
14
16
|
|
15
17
|
# background jobs
|
16
18
|
begin
|
@@ -29,7 +31,7 @@ module Searchkick
|
|
29
31
|
class ImportError < Error; end
|
30
32
|
|
31
33
|
class << self
|
32
|
-
attr_accessor :search_method_name, :wordnet_path, :timeout, :models
|
34
|
+
attr_accessor :search_method_name, :wordnet_path, :timeout, :models, :client_options
|
33
35
|
attr_writer :client, :env, :search_timeout
|
34
36
|
attr_reader :aws_credentials
|
35
37
|
end
|
@@ -37,15 +39,16 @@ module Searchkick
|
|
37
39
|
self.wordnet_path = "/var/lib/wn_s.pl"
|
38
40
|
self.timeout = 10
|
39
41
|
self.models = []
|
42
|
+
self.client_options = {}
|
40
43
|
|
41
44
|
def self.client
|
42
45
|
@client ||= begin
|
43
46
|
require "typhoeus/adapters/faraday" if defined?(Typhoeus)
|
44
47
|
|
45
|
-
Elasticsearch::Client.new(
|
48
|
+
Elasticsearch::Client.new({
|
46
49
|
url: ENV["ELASTICSEARCH_URL"],
|
47
50
|
transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}}
|
48
|
-
) do |f|
|
51
|
+
}.deep_merge(client_options)) do |f|
|
49
52
|
f.use Searchkick::Middleware
|
50
53
|
f.request :aws_signers_v4, {
|
51
54
|
credentials: Aws::Credentials.new(aws_credentials[:access_key_id], aws_credentials[:secret_access_key]),
|
@@ -72,6 +75,28 @@ module Searchkick
|
|
72
75
|
Gem::Version.new(server_version.sub("-", ".")) < Gem::Version.new(version.sub("-", "."))
|
73
76
|
end
|
74
77
|
|
78
|
+
def self.search(term = nil, options = {}, &block)
|
79
|
+
query = Searchkick::Query.new(nil, term, options)
|
80
|
+
block.call(query.body) if block
|
81
|
+
if options[:execute] == false
|
82
|
+
query
|
83
|
+
else
|
84
|
+
query.execute
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.multi_search(queries)
|
89
|
+
if queries.any?
|
90
|
+
responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
|
91
|
+
queries.each_with_index do |query, i|
|
92
|
+
query.handle_response(responses[i])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# callbacks
|
99
|
+
|
75
100
|
def self.enable_callbacks
|
76
101
|
self.callbacks_value = nil
|
77
102
|
end
|
@@ -90,7 +115,7 @@ module Searchkick
|
|
90
115
|
begin
|
91
116
|
self.callbacks_value = value
|
92
117
|
yield
|
93
|
-
|
118
|
+
indexer.perform if callbacks_value == :bulk
|
94
119
|
ensure
|
95
120
|
self.callbacks_value = previous_value
|
96
121
|
end
|
@@ -106,39 +131,8 @@ module Searchkick
|
|
106
131
|
end
|
107
132
|
|
108
133
|
# private
|
109
|
-
def self.
|
110
|
-
|
111
|
-
perform_bulk unless callbacks_value == :bulk
|
112
|
-
end
|
113
|
-
|
114
|
-
# private
|
115
|
-
def self.perform_bulk
|
116
|
-
items = queued_items
|
117
|
-
clear_queued_items
|
118
|
-
perform_items(items)
|
119
|
-
end
|
120
|
-
|
121
|
-
# private
|
122
|
-
def self.perform_items(items)
|
123
|
-
if items.any?
|
124
|
-
response = client.bulk(body: items)
|
125
|
-
if response["errors"]
|
126
|
-
first_with_error = response["items"].map do |item|
|
127
|
-
(item["index"] || item["delete"] || item["update"])
|
128
|
-
end.find { |item| item["error"] }
|
129
|
-
raise Searchkick::ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# private
|
135
|
-
def self.queued_items
|
136
|
-
Thread.current[:searchkick_queued_items] ||= []
|
137
|
-
end
|
138
|
-
|
139
|
-
# private
|
140
|
-
def self.clear_queued_items
|
141
|
-
Thread.current[:searchkick_queued_items] = []
|
134
|
+
def self.indexer
|
135
|
+
Thread.current[:searchkick_indexer] ||= Searchkick::Indexer.new
|
142
136
|
end
|
143
137
|
|
144
138
|
# private
|
@@ -150,26 +144,6 @@ module Searchkick
|
|
150
144
|
def self.callbacks_value=(value)
|
151
145
|
Thread.current[:searchkick_callbacks_enabled] = value
|
152
146
|
end
|
153
|
-
|
154
|
-
def self.search(term = nil, options = {}, &block)
|
155
|
-
query = Searchkick::Query.new(nil, term, options)
|
156
|
-
block.call(query.body) if block
|
157
|
-
if options[:execute] == false
|
158
|
-
query
|
159
|
-
else
|
160
|
-
query.execute
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def self.multi_search(queries)
|
165
|
-
if queries.any?
|
166
|
-
responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
|
167
|
-
queries.each_with_index do |query, i|
|
168
|
-
query.handle_response(responses[i])
|
169
|
-
end
|
170
|
-
end
|
171
|
-
nil
|
172
|
-
end
|
173
147
|
end
|
174
148
|
|
175
149
|
# TODO find better ActiveModel hook
|
data/lib/searchkick/index.rb
CHANGED
@@ -9,8 +9,8 @@ module Searchkick
|
|
9
9
|
@options = options
|
10
10
|
end
|
11
11
|
|
12
|
-
def create(
|
13
|
-
client.indices.create index: name, body:
|
12
|
+
def create(body = {})
|
13
|
+
client.indices.create index: name, body: body
|
14
14
|
end
|
15
15
|
|
16
16
|
def delete
|
@@ -59,16 +59,16 @@ module Searchkick
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def bulk_delete(records)
|
62
|
-
Searchkick.
|
62
|
+
Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| {delete: record_data(r)} })
|
63
63
|
end
|
64
64
|
|
65
65
|
def bulk_index(records)
|
66
|
-
Searchkick.
|
66
|
+
Searchkick.indexer.queue(records.map { |r| {index: record_data(r).merge(data: search_data(r))} })
|
67
67
|
end
|
68
68
|
alias_method :import, :bulk_index
|
69
69
|
|
70
70
|
def bulk_update(records, method_name)
|
71
|
-
Searchkick.
|
71
|
+
Searchkick.indexer.queue(records.map { |r| {update: record_data(r).merge(data: {doc: search_data(r, method_name)})} })
|
72
72
|
end
|
73
73
|
|
74
74
|
def record_data(r)
|
@@ -257,7 +257,16 @@ module Searchkick
|
|
257
257
|
end
|
258
258
|
|
259
259
|
def import_or_update(records, method_name)
|
260
|
-
|
260
|
+
retries = 0
|
261
|
+
begin
|
262
|
+
method_name ? bulk_update(records, method_name) : import(records)
|
263
|
+
rescue Faraday::ClientError => e
|
264
|
+
if retries < 1
|
265
|
+
retries += 1
|
266
|
+
retry
|
267
|
+
end
|
268
|
+
raise e
|
269
|
+
end
|
261
270
|
end
|
262
271
|
|
263
272
|
# other
|
@@ -336,9 +336,12 @@ module Searchkick
|
|
336
336
|
dynamic_fields["{name}"].merge(fields: dynamic_fields.except("{name}"))
|
337
337
|
end
|
338
338
|
|
339
|
+
# TODO make dynamic
|
340
|
+
all_enabled = true
|
341
|
+
|
339
342
|
mappings = {
|
340
343
|
_default_: {
|
341
|
-
_all: {type: default_type, index: "analyzed", analyzer: default_analyzer},
|
344
|
+
_all: all_enabled ? {type: default_type, index: "analyzed", analyzer: default_analyzer} : {enabled: false},
|
342
345
|
properties: mapping,
|
343
346
|
_routing: routing,
|
344
347
|
# https://gist.github.com/kimchy/2898285
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Searchkick
|
2
|
+
class Indexer
|
3
|
+
attr_reader :queued_items
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@queued_items = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def queue(items)
|
10
|
+
@queued_items.concat(items)
|
11
|
+
perform unless Searchkick.callbacks_value == :bulk
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
items = @queued_items
|
16
|
+
@queued_items = []
|
17
|
+
if items.any?
|
18
|
+
response = Searchkick.client.bulk(body: items)
|
19
|
+
if response["errors"]
|
20
|
+
first_with_error = response["items"].map do |item|
|
21
|
+
(item["index"] || item["delete"] || item["update"])
|
22
|
+
end.find { |item| item["error"] }
|
23
|
+
raise Searchkick::ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/searchkick/logging.rb
CHANGED
@@ -58,22 +58,12 @@ module Searchkick
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
module
|
62
|
-
def
|
63
|
-
|
64
|
-
name: "Multi Search",
|
65
|
-
body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
66
|
-
}
|
67
|
-
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
68
|
-
super
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def perform_items(items)
|
73
|
-
if callbacks_value == :bulk
|
61
|
+
module IndexerWithInstrumentation
|
62
|
+
def perform
|
63
|
+
if Searchkick.callbacks_value == :bulk
|
74
64
|
event = {
|
75
65
|
name: "Bulk",
|
76
|
-
count:
|
66
|
+
count: queued_items.size
|
77
67
|
}
|
78
68
|
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
79
69
|
super
|
@@ -84,6 +74,18 @@ module Searchkick
|
|
84
74
|
end
|
85
75
|
end
|
86
76
|
|
77
|
+
module SearchkickWithInstrumentation
|
78
|
+
def multi_search(searches)
|
79
|
+
event = {
|
80
|
+
name: "Multi Search",
|
81
|
+
body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
82
|
+
}
|
83
|
+
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
87
89
|
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
|
88
90
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
89
91
|
def self.runtime=(value)
|
@@ -178,6 +180,7 @@ module Searchkick
|
|
178
180
|
end
|
179
181
|
Searchkick::Query.send(:prepend, Searchkick::QueryWithInstrumentation)
|
180
182
|
Searchkick::Index.send(:prepend, Searchkick::IndexWithInstrumentation)
|
183
|
+
Searchkick::Indexer.send(:prepend, Searchkick::IndexerWithInstrumentation)
|
181
184
|
Searchkick.singleton_class.send(:prepend, Searchkick::SearchkickWithInstrumentation)
|
182
185
|
Searchkick::LogSubscriber.attach_to :searchkick
|
183
186
|
ActiveSupport.on_load(:action_controller) do
|
data/lib/searchkick/model.rb
CHANGED
@@ -43,21 +43,25 @@ module Searchkick
|
|
43
43
|
class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
|
44
44
|
end
|
45
45
|
|
46
|
-
def searchkick_reindex(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
def searchkick_reindex(method_name = nil, **options)
|
47
|
+
if method_name
|
48
|
+
searchkick_index.import_scope(searchkick_klass, method_name: method_name)
|
49
|
+
searchkick_index.refresh
|
50
|
+
true
|
51
|
+
else
|
52
|
+
unless options[:accept_danger]
|
53
|
+
if (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
|
54
|
+
(respond_to?(:queryable) && queryable != unscoped.with_default_scope)
|
55
|
+
raise Searchkick::DangerousOperation, "Only call reindex on models, not relations. Pass `accept_danger: true` if this is your intention."
|
56
|
+
end
|
51
57
|
end
|
58
|
+
searchkick_index.reindex_scope(searchkick_klass, options)
|
52
59
|
end
|
53
|
-
searchkick_index.reindex_scope(searchkick_klass, options)
|
54
60
|
end
|
55
61
|
alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
|
56
62
|
|
57
63
|
def searchkick_partial_reindex(method_name)
|
58
|
-
|
59
|
-
searchkick_index.refresh
|
60
|
-
true
|
64
|
+
searchkick_reindex(method_name)
|
61
65
|
end
|
62
66
|
alias_method :partial_reindex, :searchkick_partial_reindex unless method_defined?(:partial_reindex)
|
63
67
|
|
@@ -93,8 +97,13 @@ module Searchkick
|
|
93
97
|
after_destroy callback_name, if: proc { self.class.search_callbacks? }
|
94
98
|
end
|
95
99
|
|
96
|
-
def reindex
|
97
|
-
|
100
|
+
def reindex(method_name = nil, **options)
|
101
|
+
if method_name
|
102
|
+
self.class.searchkick_index.bulk_update([self], method_name)
|
103
|
+
else
|
104
|
+
self.class.searchkick_index.reindex_record(self)
|
105
|
+
end
|
106
|
+
self.class.searchkick_index.refresh if options[:refresh]
|
98
107
|
end unless method_defined?(:reindex)
|
99
108
|
|
100
109
|
def reindex_async
|
@@ -102,8 +111,7 @@ module Searchkick
|
|
102
111
|
end unless method_defined?(:reindex_async)
|
103
112
|
|
104
113
|
def partial_reindex(method_name)
|
105
|
-
|
106
|
-
self.class.searchkick_index.refresh
|
114
|
+
reindex(method_name, refresh: true)
|
107
115
|
true
|
108
116
|
end unless method_defined?(:partial_reindex)
|
109
117
|
|
data/lib/searchkick/version.rb
CHANGED
data/test/boost_test.rb
CHANGED
@@ -164,6 +164,6 @@ class BoostTest < Minitest::Test
|
|
164
164
|
store_names ["Rex"], Animal
|
165
165
|
store_names ["Rexx"], Product
|
166
166
|
|
167
|
-
assert_order "Rex", ["Rexx", "Rex"], {index_name: [Animal, Product], indices_boost: {Animal => 1, Product => 200}}, Store
|
167
|
+
assert_order "Rex", ["Rexx", "Rex"], {index_name: [Animal, Product], indices_boost: {Animal => 1, Product => 200}, fields: [:name]}, Store
|
168
168
|
end
|
169
169
|
end
|
data/test/highlight_test.rb
CHANGED
@@ -41,10 +41,10 @@ class HighlightTest < Minitest::Test
|
|
41
41
|
assert_equal "<b><em>Hello</em></b>", Product.search("hello", fields: [:name], highlight: {encoder: "html"}, misspellings: false).with_details.first[1][:highlight][:name]
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def test_body
|
45
45
|
skip if ENV["MATCH"] == "word_start"
|
46
46
|
store_names ["Two Door Cinema Club"]
|
47
|
-
|
47
|
+
body = {
|
48
48
|
query: {
|
49
49
|
match: {
|
50
50
|
"name.analyzed" => "cinema"
|
@@ -58,6 +58,6 @@ class HighlightTest < Minitest::Test
|
|
58
58
|
}
|
59
59
|
}
|
60
60
|
}
|
61
|
-
assert_equal "Two Door <strong>Cinema</strong> Club", Product.search(
|
61
|
+
assert_equal "Two Door <strong>Cinema</strong> Club", Product.search(body: body).with_details.first[1][:highlight][:"name.analyzed"]
|
62
62
|
end
|
63
63
|
end
|
data/test/index_test.rb
CHANGED
@@ -93,7 +93,7 @@ class IndexTest < Minitest::Test
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_missing_index
|
96
|
-
assert_raises(Searchkick::MissingIndexError) { Product.search
|
96
|
+
assert_raises(Searchkick::MissingIndexError) { Product.search("test", index_name: "not_found") }
|
97
97
|
end
|
98
98
|
|
99
99
|
def test_unsupported_version
|
@@ -103,8 +103,8 @@ class IndexTest < Minitest::Test
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
-
def
|
107
|
-
assert_raises(Searchkick::InvalidQueryError) { Product.search(
|
106
|
+
def test_invalid_body
|
107
|
+
assert_raises(Searchkick::InvalidQueryError) { Product.search(body: {boom: true}) }
|
108
108
|
end
|
109
109
|
|
110
110
|
def test_transaction
|
@@ -22,7 +22,7 @@ class PartialReindexTest < Minitest::Test
|
|
22
22
|
assert_search "blue", ["Hi"], fields: [:color], load: false
|
23
23
|
|
24
24
|
# partial reindex
|
25
|
-
Product.
|
25
|
+
Product.reindex(:search_name)
|
26
26
|
|
27
27
|
# name updated, but not color
|
28
28
|
assert_search "bye", ["Bye"], fields: [:name], load: false
|
@@ -49,7 +49,7 @@ class PartialReindexTest < Minitest::Test
|
|
49
49
|
assert_search "hi", ["Hi"], fields: [:name], load: false
|
50
50
|
assert_search "blue", ["Hi"], fields: [:color], load: false
|
51
51
|
|
52
|
-
product.
|
52
|
+
product.reindex(:search_name, refresh: true)
|
53
53
|
|
54
54
|
# name updated, but not color
|
55
55
|
assert_search "bye", ["Bye"], fields: [:name], load: false
|
data/test/sql_test.rb
CHANGED
@@ -76,9 +76,9 @@ class SqlTest < Minitest::Test
|
|
76
76
|
assert_equal "Product A", Product.search("product", load: false).first.name
|
77
77
|
end
|
78
78
|
|
79
|
-
def
|
79
|
+
def test_load_false_with_includes
|
80
80
|
store_names ["Product A"]
|
81
|
-
assert_kind_of Hash, Product.search("product", load: false,
|
81
|
+
assert_kind_of Hash, Product.search("product", load: false, includes: [:store]).first
|
82
82
|
end
|
83
83
|
|
84
84
|
def test_load_false_nested_object
|
@@ -227,16 +227,15 @@ class SqlTest < Minitest::Test
|
|
227
227
|
# nested
|
228
228
|
|
229
229
|
def test_nested_search
|
230
|
-
|
231
|
-
|
232
|
-
assert_search "frozen", ["Product A"], fields: ["aisle.name"], debug: true
|
230
|
+
store [{name: "Product A", aisle: {"id" => 1, "name" => "Frozen"}}], Speaker
|
231
|
+
assert_search "frozen", ["Product A"], {fields: ["aisle.name"]}, Speaker
|
233
232
|
end
|
234
233
|
|
235
234
|
# other tests
|
236
235
|
|
237
|
-
def
|
236
|
+
def test_includes
|
238
237
|
skip unless defined?(ActiveRecord)
|
239
238
|
store_names ["Product A"]
|
240
|
-
assert Product.search("product",
|
239
|
+
assert Product.search("product", includes: [:store]).first.association(:store).loaded?
|
241
240
|
end
|
242
241
|
end
|
data/test/test_helper.rb
CHANGED
@@ -268,6 +268,7 @@ else
|
|
268
268
|
end
|
269
269
|
|
270
270
|
class Product < ActiveRecord::Base
|
271
|
+
belongs_to :store
|
271
272
|
end
|
272
273
|
|
273
274
|
class Store < ActiveRecord::Base
|
@@ -291,8 +292,6 @@ else
|
|
291
292
|
end
|
292
293
|
|
293
294
|
class Product
|
294
|
-
belongs_to :store
|
295
|
-
|
296
295
|
searchkick \
|
297
296
|
synonyms: [
|
298
297
|
["clorox", "bleach"],
|
@@ -317,6 +316,7 @@ class Product
|
|
317
316
|
word_end: [:name],
|
318
317
|
highlight: [:name],
|
319
318
|
searchable: [:name, :color],
|
319
|
+
default_fields: [:name, :color],
|
320
320
|
filterable: [:name, :color, :description],
|
321
321
|
# unsearchable: [:description],
|
322
322
|
# only_analyzed: [:alt_description],
|
@@ -388,12 +388,13 @@ class Speaker
|
|
388
388
|
searchkick \
|
389
389
|
conversions: ["conversions_a", "conversions_b"]
|
390
390
|
|
391
|
-
attr_accessor :conversions_a, :conversions_b
|
391
|
+
attr_accessor :conversions_a, :conversions_b, :aisle
|
392
392
|
|
393
393
|
def search_data
|
394
394
|
serializable_hash.except("id").merge(
|
395
395
|
conversions_a: conversions_a,
|
396
|
-
conversions_b: conversions_b
|
396
|
+
conversions_b: conversions_b,
|
397
|
+
aisle: aisle
|
397
398
|
)
|
398
399
|
end
|
399
400
|
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: 1.5.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -111,6 +111,7 @@ files:
|
|
111
111
|
- lib/searchkick.rb
|
112
112
|
- lib/searchkick/index.rb
|
113
113
|
- lib/searchkick/index_options.rb
|
114
|
+
- lib/searchkick/indexer.rb
|
114
115
|
- lib/searchkick/logging.rb
|
115
116
|
- lib/searchkick/middleware.rb
|
116
117
|
- lib/searchkick/model.rb
|
@@ -139,6 +140,7 @@ files:
|
|
139
140
|
- test/gemfiles/mongoid3.gemfile
|
140
141
|
- test/gemfiles/mongoid4.gemfile
|
141
142
|
- test/gemfiles/mongoid5.gemfile
|
143
|
+
- test/gemfiles/mongoid6.gemfile
|
142
144
|
- test/gemfiles/nobrainer.gemfile
|
143
145
|
- test/geo_shape_test.rb
|
144
146
|
- test/highlight_test.rb
|
@@ -209,6 +211,7 @@ test_files:
|
|
209
211
|
- test/gemfiles/mongoid3.gemfile
|
210
212
|
- test/gemfiles/mongoid4.gemfile
|
211
213
|
- test/gemfiles/mongoid5.gemfile
|
214
|
+
- test/gemfiles/mongoid6.gemfile
|
212
215
|
- test/gemfiles/nobrainer.gemfile
|
213
216
|
- test/geo_shape_test.rb
|
214
217
|
- test/highlight_test.rb
|