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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 915b713c1d61ef9709e1c95f7c588b49013b4cdd
4
- data.tar.gz: cdbefb8ecb273fc877de64cc25356bcd13bf02a8
3
+ metadata.gz: 3fa47681f8cf22fcc751a944326bfbd16f437a55
4
+ data.tar.gz: 270f29030b412fb937c33c04e00069982c48e3c9
5
5
  SHA512:
6
- metadata.gz: 961fed20bdf8f9227f735b58fa5fd0f29566ca57396f45eaf7c9779749a2f637510b65187064e0b607d64847572a447729d9c56df08df2317a1e1814d787c3f8
7
- data.tar.gz: d1b7c56bc92cc95e8308dd35bdd365d457217bf0e272f2cedd3d6cf625b8f1c715e0687859be8e005c1f828f3df00f6a150dc7d5f37a271cf508741f19e473cd
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.2.5
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
@@ -1,3 +1,9 @@
1
+ ## 1.5.1
2
+
3
+ - Added `client_options`
4
+ - Added `refresh` option to `reindex` method
5
+ - Improved syntax for partial reindex
6
+
1
7
  ## 1.5.0
2
8
 
3
9
  - Added support for geo shape indexing and queries
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
- The `geo_shape` hash is passed through to Elasticsearch without modification. Please see the [geo shape documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for options.
929
+ See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html) for details.
930
930
 
931
- Any geospatial data type can be held in the index or give as a search query. It is up to you to ensure that it is a valid GeoJSON representation. Possible shapes are:
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
- ### Performance
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
- require "typhoeus/adapters/faraday"
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
- ### Automatic Failover
1150
+ ### Searchable Fields
1158
1151
 
1159
- Create an initializer `config/initializers/elasticsearch.rb` with multiple hosts:
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
- Searchkick.client = Elasticsearch::Client.new(hosts: ["localhost:9200", "localhost:9201"], retry_on_failure: true)
1155
+ class Product < ActiveRecord::Base
1156
+ searchkick searchable: [:name]
1157
+ end
1163
1158
  ```
1164
1159
 
1165
- See [elasticsearch-transport](https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-transport) for a complete list of options.
1160
+ ### Filterable Fields
1166
1161
 
1167
- ### Lograge
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
- Add the following to `config/environments/production.rb`:
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
- config.lograge.custom_options = lambda do |event|
1173
- options = {}
1174
- options[:search] = event.payload[:searchkick_runtime] if event.payload[:searchkick_runtime].to_f > 0
1175
- options
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
- See [Production Rails](https://github.com/ankane/production_rails) for other good practices.
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 "*", where: {"address.zip_code" => 12345}
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.partial_reindex(:search_prices)
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 [Okapi BM25](https://www.elastic.co/guide/en/elasticsearch/guide/current/pluggable-similarites.html) for ranking
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: "BM25"
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: proc{|model| model.name_changed? } # use your own condition
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 # don't forget this
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
- perform_bulk if callbacks_value == :bulk
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.queue_items(items)
110
- queued_items.concat(items)
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
@@ -9,8 +9,8 @@ module Searchkick
9
9
  @options = options
10
10
  end
11
11
 
12
- def create(options = {})
13
- client.indices.create index: name, body: options
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.queue_items(records.reject { |r| r.id.blank? }.map { |r| {delete: record_data(r)} })
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.queue_items(records.map { |r| {index: record_data(r).merge(data: search_data(r))} })
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.queue_items(records.map { |r| {update: record_data(r).merge(data: {doc: search_data(r, method_name)})} })
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
- method_name ? bulk_update(records, method_name) : import(records)
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
@@ -58,22 +58,12 @@ module Searchkick
58
58
  end
59
59
  end
60
60
 
61
- module SearchkickWithInstrumentation
62
- def multi_search(searches)
63
- event = {
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: items.size
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
@@ -43,21 +43,25 @@ module Searchkick
43
43
  class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
44
44
  end
45
45
 
46
- def searchkick_reindex(options = {})
47
- unless options[:accept_danger]
48
- if (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
49
- (respond_to?(:queryable) && queryable != unscoped.with_default_scope)
50
- raise Searchkick::DangerousOperation, "Only call reindex on models, not relations. Pass `accept_danger: true` if this is your intention."
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
- searchkick_index.import_scope(searchkick_klass, method_name: method_name)
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
- self.class.searchkick_index.reindex_record(self)
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
- self.class.searchkick_index.bulk_update([self], method_name)
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
 
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "1.5.0"
2
+ VERSION = "1.5.1"
3
3
  end
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
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in searchkick.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "mongoid", "~> 6.0.0"
@@ -41,10 +41,10 @@ class HighlightTest < Minitest::Test
41
41
  assert_equal "&lt;b&gt;<em>Hello</em>&lt;&#x2F;b&gt;", Product.search("hello", fields: [:name], highlight: {encoder: "html"}, misspellings: false).with_details.first[1][:highlight][:name]
42
42
  end
43
43
 
44
- def test_json
44
+ def test_body
45
45
  skip if ENV["MATCH"] == "word_start"
46
46
  store_names ["Two Door Cinema Club"]
47
- json = {
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(json: json).with_details.first[1][:highlight][:"name.analyzed"]
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 "test", index_name: "not_found" }
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 test_invalid_query
107
- assert_raises(Searchkick::InvalidQueryError) { Product.search(query: {boom: true}) }
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.partial_reindex(:search_name)
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.partial_reindex(:search_name)
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 test_load_false_with_include
79
+ def test_load_false_with_includes
80
80
  store_names ["Product A"]
81
- assert_kind_of Hash, Product.search("product", load: false, include: [:store]).first
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
- skip
231
- store [{name: "Product A", aisle: {"id" => 1, "name" => "Frozen"}}]
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 test_include
236
+ def test_includes
238
237
  skip unless defined?(ActiveRecord)
239
238
  store_names ["Product A"]
240
- assert Product.search("product", include: [:store]).first.association(:store).loaded?
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.0
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-23 00:00:00.000000000 Z
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