searchkick 1.5.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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